An Introduction to Redis with Python

In this notebook, we will go thourhg a similar set of commands as those described in the Redis Data Types introduction but using the redis-py Python client from a Jupyter notebook.

Remember that Redis is a server, and it can be access in a distributed way by multiple clients in an Enterprise System. This notebook acts as a single client, and is just for educative purposes. The full power of Redis comes when used in an enterprise architecture!

While working with Redis with Python, you will notice that many operations on Redis data types are also available for the Python data types that we get as a result of some operations (e.g. lists). However we have to keep in mind that they operate at very different levels. Using the Redis server operations, and not the local Python equivalents, is the way to go for enterprise applications in order to keep our system scalability and availability (i.e. large sets, concurrent access, etc).

Starting up

Interacting with a running Redis server from a Jupyter notebook using Python is as easy as installing redis-py for your Python distribution and then import the module as follows.


In [1]:
import redis

Then we can obtain a reference to our server. Asuming that we are running our Redis server and our Jupyter notebook server in the same host, with the default Redis server port, we can do as follows.


In [2]:
r = redis.StrictRedis(host='localhost', port=6379, db=0)

Getting and settings Redis Keys

Now we can use r to send Redis commands. For example, we can SET the value of the key my.key as follows.


In [3]:
r.set('my.key', 'value1')


Out[3]:
True

In order to check our recently set key, we can use GET and pass the name of the key.


In [4]:
r.get('my.key')


Out[4]:
'value1'

We can also check for the existence of a given key.


In [5]:
r.exists('my.key')


Out[5]:
True

In [6]:
r.exists('some.other.key')


Out[6]:
False

If we want to set multiple keys at once, we can use MSET and pass a Python dictionary as follows.


In [7]:
r.mset({'my.key':'value2', 'some.other.key':123})


Out[7]:
True

In [8]:
r.get('my.key')


Out[8]:
'value2'

In [9]:
r.get('some.other.key')


Out[9]:
'123'

We can also increment the value of a given key in an atomic way.


In [10]:
r.incrby('some.other.key',10)


Out[10]:
133

Notice how the resulting type has been changed to integer!

Setting keys to expire

With redis-py we can also set keys with limited time to live.


In [11]:
r.expire('some.other.key',1)
r.exists('some.other.key')


Out[11]:
True

Let's wait for a couple of seconds for the key to expire and check again.


In [12]:
from time import sleep
sleep(2)
r.exists('some.other.key')


Out[12]:
False

Finally, del is a reserved keyword in the Python syntax. Therefore redis-py uses 'delete' instead.


In [13]:
r.delete('my.key')


Out[13]:
1

Redis Lists

Redis lists are linked lists of keys. We can insert and remove elements from both ends.

The LPUSH command adds a new element into a list, on the left.


In [14]:
r.lpush('my.list', 'elem1')


Out[14]:
1L

The RPUSH command adds a new element into a list, on the right.


In [15]:
r.rpush('my.list', 'elem2')


Out[15]:
2L

Finally the LRANGE command extracts ranges of elements from lists.


In [16]:
r.lrange('my.list',0,-1)


Out[16]:
['elem1', 'elem2']

In [17]:
r.lpush('my.list', 'elem0')


Out[17]:
3L

In [18]:
r.lrange('my.list',0,-1)


Out[18]:
['elem0', 'elem1', 'elem2']

The result is returned as a Python list. We can use LLEN to check a Redis list lenght without requiring to store the result of lrange and then use Python's len.


In [19]:
r.llen('my.list')


Out[19]:
3

We can push multiple elements with a single call to push.


In [20]:
r.rpush('my.list','elem3','elem4')


Out[20]:
5L

In [21]:
r.lrange('my.list',0,-1)


Out[21]:
['elem0', 'elem1', 'elem2', 'elem3', 'elem4']

Finally, we have the equivalent pop operations for both, right and left ends.


In [22]:
r.lpop('my.list')


Out[22]:
'elem0'

In [23]:
r.lrange('my.list',0,-1)


Out[23]:
['elem1', 'elem2', 'elem3', 'elem4']

In [24]:
r.rpop('my.list')


Out[24]:
'elem4'

In [25]:
r.lrange('my.list',0,-1)


Out[25]:
['elem1', 'elem2', 'elem3']

Capped Lists

We can also TRIM Redis lists with redis-py. We need to pass three arguments: the name of the list, and the start and stop indexes.


In [26]:
r.lpush('my.list','elem0')
r.ltrim('my.list',0,2)


Out[26]:
True

In [27]:
r.lrange('my.list',0,-1)


Out[27]:
['elem0', 'elem1', 'elem2']

Notice as the last element has been dropped when triming the list. The lpush/ltrim sequence is a common pattern when inserting in a list that we want to keep size-fized.


In [28]:
r.delete('my.list')


Out[28]:
1

Redis Hashes

The equivalent of Python dictionaries are Redis hashes, with field-value pairs. We use the command HMSET.


In [29]:
r.hmset('my.hash', {'field1':'value1',
                   'field2': 1234})


Out[29]:
True

We can also set individual fields.


In [30]:
r.hset('my.hash','field3',True)


Out[30]:
0L

We have methods to get individual and multiple fields from a hash.


In [31]:
r.hget('my.hash','field2')


Out[31]:
'1234'

In [32]:
r.hmget('my.hash','field1','field2','field3')


Out[32]:
['value1', '1234', 'True']

The result is returned as a list of values.

Increment operations are also available for hash fields.


In [33]:
r.hincrby('my.hash','field2',10)


Out[33]:
1244L

Redis Sets

Redis Sets are unordered collections of strings. We can easily add multiple elements to a Redis set in redis-py as follows by using its implementation of SADD.


In [34]:
r.sadd('my.set', 1, 2, 3)


Out[34]:
3

As a result, we get the size of the set. If we want to check the elements within a set, we can use SMEMBERS.


In [35]:
r.smembers('my.set')


Out[35]:
{'1', '2', '3'}

In [36]:
type(r.smembers('my.set'))


Out[36]:
set

Notice that we get a Python set as a result. That opens the door to all sort of Python set operations. However, we can operate directly within the Redis server space, and still do things as checking an element membership using SISMEMBER. This is the way to go for enterprise applications in order to keep our system scalability and availability (i.e. large sets, concurrent access, etc).


In [37]:
r.sismember('my.set', 4)


Out[37]:
False

In [38]:
r.sismember('my.set', 1)


Out[38]:
True

The SPOP command extracts a random element (and we can use SRANDMEMBER to get one or more random elements without extraction).


In [39]:
elem = r.spop('my.set')

In [40]:
r.smembers('my.set')


Out[40]:
{'1', '2'}

In [41]:
r.sadd('my.set',elem)


Out[41]:
1

In [42]:
r.smembers('my.set')


Out[42]:
{'1', '2', '3'}

Or if we want to be specific, we can just use SREM.


In [43]:
r.srem('my.set',2)


Out[43]:
1

In [44]:
r.smembers('my.set')


Out[44]:
{'1', '3'}

Set operations

In order to obtain the intersection between two sets, we can use SINTER.


In [45]:
r.sadd('my.other.set', 'A','B',1)


Out[45]:
3

In [46]:
r.smembers('my.other.set')


Out[46]:
{'1', 'A', 'B'}

In [47]:
r.sinter('my.set','my.other.set')


Out[47]:
{'1'}

That we get as a Python set. Alternatively, we can directly store the result as a new Redis set by using SINTERSTORE.


In [48]:
r.sinterstore('my.intersection','my.set','my.other.set')


Out[48]:
1

In [49]:
r.smembers('my.intersection')


Out[49]:
{'1'}

Similar operations are available for union and difference. Moreover, they can be applied to more than two sets. For example, let's create a union set with all the previous and store it in a new Redis set.


In [50]:
r.sadd('my.intersection','batman')
r.sunionstore('my.union','my.set','my.other.set','my.intersection')


Out[50]:
5

In [51]:
r.smembers('my.union')


Out[51]:
{'1', '3', 'A', 'B', 'batman'}

Finally, the number of elements of a given Redis set can be obtained with SCARD.


In [52]:
r.scard('my.union')


Out[52]:
5

Let's clean our server before leaving this section.


In [53]:
r.delete('my.set','my.other.set','my.intersection','my.union')


Out[53]:
4

Redis sorted Sets

In a Redis sorted set, every element is associated with a floating point value, called the score. Elements within the set are then ordered according to these scores. We add values to a sorted set by using the oepration ZADD.


In [54]:
r.zadd('my.sorted.set', 1, 'first')
r.zadd('my.sorted.set', 3, 'third')
r.zadd('my.sorted.set', 2, 'second')
r.zadd('my.sorted.set', 4, 'fourth')
r.zadd('my.sorted.set', 6, 'sixth')


Out[54]:
1

Sorted sets' scores can be updated at any time. Just calling ZADD against an element already included in the sorted set will update its score (and position).

It doesn't matter the order in which we insert the elements. When retrieving them using ZRANGE, they will be returned as a Python list ordered by score.


In [55]:
r.zrange('my.sorted.set',0,-1)


Out[55]:
['first', 'second', 'third', 'fourth', 'sixth']

And if we want also them in reverse order, we can call ZREVRANGE.


In [56]:
r.zrevrange('my.sorted.set',0,-1)


Out[56]:
['sixth', 'fourth', 'third', 'second', 'first']

Even more, we can slice the range by score by using ZRANGEBYSCORE.


In [57]:
r.zrangebyscore('my.sorted.set',2,4)


Out[57]:
['second', 'third', 'fourth']

A similar schema can be used to remove elements from the sorted set by score using ZREMRANGEBYSCORE.


In [58]:
r.zremrangebyscore('my.sorted.set',6,'inf')
r.zrange('my.sorted.set',0,-1)


Out[58]:
['first', 'second', 'third', 'fourth']

It is also possible to ask what is the position of an element in the set of the ordered elements by using ZRANK (or ZREVRANK if we want the order in reverse way).


In [59]:
r.zrank('my.sorted.set','third')


Out[59]:
2

Remember that ranks and scores have the same order but different values! If what we want is the score, we can use ZSCORE.


In [60]:
r.zscore('my.sorted.set','third')


Out[60]:
3.0

Finally, there also a series of operations that operate on a sorted set in a lexicographical basis. They work when all the elements in the ser are inserted with the same value. For example, we can list the elements in the set sliced by its inital with ZRANGEBYLEX.